|
このテクニカルノートでは、伸張シーケンスを使って、RGB オフスクリーン領域に DV フレームを伸張し、フレームの各ピクセルに直接アクセスする方法を説明します。
このテクニカルノートにおける手順は、 画像記述で表された、QuickTime がサポートする任意の圧縮画像形式に適用できます。圧縮された画像データは、QuickTime Movie、Sequence Grabber またはアプリケーションが提供するバッファから引き出せます。
更新:[2002 年 4 月 11 日]
|
はじめに
単一の DV フレームを、バッファから RGB オフスクリーン領域に伸張した後、ピクセルに直接アクセスする全体の手順は簡単です。定期伸張を考慮しない場合はなおさらです。ただし、その方法は 2 通りあります。たとえば、各フレームを 1 つの画像として扱い、DecompressImage または FDecompressImage を使って、この「単一のフレーム」をピクセルマップに伸張することができます。これは、単一の画像に対して行うにはいい方法ですが、ImageDescription 構造体で表された共通の画像記述を使う、連続する同じ形式の画像を伸張する最速の方法ではありません。
より効率的な方法は、伸張シーケンスを使うことです。
Image Compression Manager (ICM)が提供する API セットを使用すれば、共通の画像記述を使う画像のシーケンスを伸張できます。シーケンスの各画像はフレームと呼ばれます。伸張シーケンスは、DecompressSequenceBeginS を呼び出すことによって開始し、シーケンスの各フレームは DecompressSequenceFrameWhen を呼び出すことによって処理されます。完了すると、CDSequenceEnd が呼び出され、シーケンスが終了します。CDSequenceBusy を呼び出せば、現在の処理の状況を確認できます。伸張シーケンスを制御するパラメータを操作できる DSequence Parameter API もいくつかあります。たとえば、ソースレクタングル、変換行列、精度などを操作できます。
開始
画像を伸張する場所を決め、ソースデータを記述する画像記述を作成し、伸張対象の画像の量を決めた後、処理のマッピング行列を作成します。これらのパラメータは、伸張リクエストを行うときに、DecompressSequenceBeginS の呼び出しに指定しなければなりません。
伸張先は、グラフィックスポートとして指定し、画像ソースのサイズはソース画像の座標系におけるレクタングルで表します。ソース画像全体を示すときには、NULL を使用できます。様々なサイズの伸張先に合わせた拡大縮小などの変換をサポートする場合は、画像を伸張先のグラフィックスポートにマッピングする方法を表す行列を指定します。
重要:
クロッピングされ、拡大縮小されていない DV をより小さい伸張先(1/4 サイズなど)に描画するために、コードがより小さいソースレクタングル(1/4 サイズなど)を渡すと、QuickTime 5 の DV 固有のバグにより、この伸張リクエストが誤って解釈され、このレクタングルに合うようにフレームが拡大縮小されてしまいます。このリクエストの正しい解釈は、通常のサイズにクロッピングされた DV フレームの左上隅を描画することです。
このバグは QuickTime 6 で修正される予定です。このバグが原因で、お使いのコードが上記のように動作した場合は、コードを必ず修正し、DecompressSequenceBeginS の呼び出しの中で行列を使用し、フレームがオフスクリーンの GWorld に合わせて拡大縮小されるようにしてください。このアプローチは、すべてのバージョンの QuickTime で正しく動作します。
|
画像に関連付けられた画像記述構造体を調べることで、ソース画像のサイズが分かります。また、その画像データを熟知している場合は、画像記述を自分で作成することもできます。画像記述構造体には、圧縮された画像またはシーケンスの特性を定義している情報が含まれます。1 つの画像記述構造体は、1 つまたは複数の圧縮されたフレームに関連付けられている場合があります。図 1 を参照してください。
画像記述拡張も含め、画像記述構造体における解凍された Y´CbCr ビデオの表現の詳細については、流氷通信 #19 を参照してください。
ImageDescription
圧縮データの画像記述を作成します。リスト 1 を参照してください。
struct ImageDescription {
long idSize; // 追加的なデータ(シーケンスデータごとの CLUT やその他の
// データ)を含む ImageDescription の合計サイズ
CodecType cType; // このデータを圧縮したコーデックの種類
long resvd1; // Apple での使用のため、予約されている
short resvd2; // Apple での使用のため、予約されている
short dataRefIndex; // ゼロに設定
short version; // このデータのバージョン
short revisionLevel; // このデータを圧縮したコーデックのバージョン
long vendor; // このデータを圧縮したコーデックのメーカー
CodecQ temporalQuality; // 一時的な品質因子
CodecQ spatialQuality; // 空間的な品質因子
short width; // このデータの横のピクセル数
short height; // このデータの縦のピクセル数
Fixed hRes; // 水平解像度
Fixed vRes; // 垂直解像度
long dataSize; // 分かる場合は、この画像記述子の
// データサイズ
short frameCount; // この記述を適用するフレーム数
Str31 name; // コーデック名(インストールされていない場合)
short depth; // このデータの深さ(1 〜 32)または
// (33 〜 40 グレイスケール)
short clutID; // CLUT ID か、0 ならば CLUT が後に続き
// CLUT がない場合は -1 を指定
};
|
図 1 画像記述構造体
|
// 圧縮されたデータを表す画像記述を作成する。
// 戻されるハンドルを破棄するのは作成者の責任。
// 画像記述の値を変更し、PAL (kDVCPALCodecType) など、
// 他の任意の圧縮データ形式に対応できるようにする。
ImageDescriptionHandle MakeImageDescriptionForNTSCDV(void)
{
ImageDescriptionHandle hImageDescription = NULL;
hImageDescription =
(ImageDescriptionHandle)
NewHandleClear(sizeof(ImageDescription));
if (NULL != hImageDescription) {
(**hImageDescription).idSize = sizeof(ImageDescription);
(**hImageDescription).cType = kDVCNTSCCodecType;
// DV に temporalQuality がない
(**hImageDescription).temporalQuality = 0;
(**hImageDescription).spatialQuality = codecNormalQuality;
(**hImageDescription).width = 720;
(**hImageDescription).height = 480;
(**hImageDescription).hRes = 72 << 16;
(**hImageDescription).vRes = 72 << 16;
(**hImageDescription).frameCount = 1;
(**hImageDescription).depth = 24;
(**hImageDescription).clutID = -1;
}
return hImageDescription;
}
|
リスト 1 圧縮データを説明する画像記述の作成
|
オフスクリーン
QTNewGWorld を使って、伸張先のオフスクリーン領域を作成します。図 2 を参照してください。DV コーデックの場合、24 ビット RGB 形式(k24RGBPixelFormat )は最良の伸張先ではなく、32 ビット RGB 形式(k32ARGBPixelFormat )の方がより効率的な(より速い)伸張先である点に注意してください。
32 ビット RGB 形式のオフスクリーンを作成します。リスト 2 を参照してください。
OSErr QTNewGWorld(
GWorldPtr *offscreenGWorld, // GWorld へのポインタが戻される
OSType PixelFormat; // 新しい GWorld のピクセル形式
const Rect *boundsRect, // 境界およびポートレクタングル
CTabHandle cTable, // ColorTable。デフォルトは NULL
GDHandle aGDevice, // GDevice。NULL に設定
GWorldFlags flags); // フラグ。デフォルトは 0
|
図 2 QTNewGWorld
API
|
// 出力先として 32 ビット GWorld を作成する(DV の場合は 720 x 480)。
// この関数はピクセルマップをロックする。
OSErr MakeGWorld(short inWidth, short inHeight,
GWorldPtr *outDestGWorld)
{
Rect theBounds = {0, 0};
OSErr err = noErr;
theBounds.right = inWidth;
theBounds.bottom = inHeight;
*outDestGWorld = NULL;
err = QTNewGWorld(outDestGWorld, // オフスクリーンへの
// ポインタが戻される
k32ARGBPixelFormat, // 新しい GWorld のピクセル形式
&theBounds, // オフスクリーン PixMap の境界
// およびポートレクタングル
NULL, // ColorTable のハンドル
NULL, // GDevice のハンドル
0); // フラグ
if (noErr == err)
// LockPixels を呼び出し、ピクセルマップからのコピー中
// または描画中に、オフスクリーンのピクセル画像の
// ベースアドレスが移動しないようにする。
LockPixels(GetGWorldPixMap(*outDestGWorld));
return err;
}
|
リスト 2 伸張先として 32 ビット
GWorld を作成
|
シーケンスのセットアップ
ICM の DecompressSequenceBeginS 関数を呼び出し、画像シーケンスの伸張に必要なセットアップを行います。図 3 を参照してください。DecompressSequenceBegin も使用できますが、ここでは DecompressSequenceBeginS を代わりに使用しています。
DecompressSequenceBeginS の呼び出しは、シーケンスを開始するために使われ、シーケンスの伸張処理を制御する多くのパラメータを指定します。ICM は、この処理に必要なシステムリソースを割り当て、シーケンスを一意に特定する ImageSequence ID (seqID )を戻します。
伸張シーケンスを開始します。リスト 3 を参照してください。
OSErr DecompressSequenceBeginS(
ImageSequence *seqID, // 一意の seqID を戻す
ImageDescriptionHandle desc, // 圧縮されたデータの記述
Ptr data, // 前処理に使う
// 圧縮データへのポインタ
long dataSize, // データバッファのサイズ
CGrafPtr port, // 伸張先ポート
GDHandle gdh, // 伸張先の GDevice
const Rect *srcRect, // 画像の伸張対象範囲
MatrixRecordPtr matrix, // 伸張時に適用する変換
short mode, // 処理のグラフィックス転送モード
RgnHandle mask, // 伸張時に適用されるマスク
CodecFlags flags, // 中間バッファの割り当てフラグ
CodecQ accuracy // 処理に求められる精度
DecompressorComponent codec); // 使用するデコンプレッサ
// 特殊な識別子の場合もある
|
図 3
DecompressionSequenceBeginS API
|
// 一連のフレームの伸張処理を開始することを通知する。
// CodecQ パラメータに codecHighQuality を使うと、デコンプレッサに、
// 妥当なパフォーマンスで達成できる最高の画像品質でレンダリング
// するように指示できる。速度を優先するときは、CodeQ の設定値を
// 小さくするとよい場合もある。
OSErr MakeDecompressionSequence(
ImageDescriptionHandle inImageDescription,
GWorldPtr inDestGWorld, ImageSequence *outSeqID)
{
Rect theSrcBounds = {0, 0};
Rect theDestBounds;
MatrixRecord rMatrix;
*outSeqID = 0;
if (NULL == inImageDescription) return paramErr;
// *** DV ソースに関する注意事項 ***
// クロッピングされ、拡大縮小されていない DV をより小さい伸張先
// (1/4 サイズなど)に描画するために、コードがより小さいソース
// レクタングル(1/4 サイズなど)を渡すと、QuickTime 5 の DV 固有
// のバグにより、この伸張リクエストが誤って解釈され、このレクタングルに
// 合うようにフレームが拡大縮小されてしまう。このリクエストの
// 正しい解釈は、通常のサイズにクロッピングされた DV フレームの
// 左上隅を描画する。このバグは QuickTime 6 で修正される予定。
// このバグが原因で、お使いのコードが上記のように動作した場合は、
// コードを必ず修正し、DecompressSequenceBeginS の呼び出しの中で
// 行列を使用し、フレームがオフスクリーンの GWorld に対して拡大縮小
// されるようにすること。このアプローチは、すべてのバージョンの
// QuickTime で正しく動作する。
// *************************************
// ソースの境界から伸張先の境界に合わせて拡大縮小するための変換行列を
// 作成する。DecompressSequenceBeginS の呼び出しのソースレクタングルに
// NULL を使うと、ソース画像全体を伸張するように指定できる。
GetPortBounds(inDestGWorld, &theDestBounds);
theSrcBounds.right = (*inImageDescription)->width;
theSrcBounds.bottom = (*inImageDescription)->height;
RectMatrix(&rMatrix, &theSrcBounds, &theDestBounds);
return DecompressSequenceBeginS(
outSeqID, // シーケンスの一意の ID を受け取る
// フィールドへのポインタ
inImageDescription, // 画像記述の構造体に対する
// ハンドル
NULL, // 圧縮された画像データへの
// ポインタ(前処理に使用)
0, // 画像データのサイズ
inDestGWorld, // 伸張先の画像のポート
NULL, // グラフィックスデバイスのハンドル。
// ポートが設定されている場合は、
// NULL に設定
NULL, // 画像の伸張対象の範囲
// 定義するソースレクタングル。
// 画像全体を指定する場合は NULL
&rMatrix, // 変換行列
srcCopy, // 転送モードの指定子
(RgnHandle)NULL, // マスクとして使用するクリッピング
// リージョンを出力先の座標系で表す
0, // フラグ
codecHighQuality, // 伸張における精度
anyCodec); // コンプレッサの識別子、または
// bestSpeedCodec などの特殊な識別子
}
|
リスト 3 フレームのシーケンスの伸張処理の開始を通知する
|
GWorldPtr から返された QTNewGWorld を、伸張先のポートのパラメータとして使います。CodecQ パラメータに codecHighQuality を使うと、デコンプレッサに、妥当なパフォーマンスで達成できる最高の画像品質でレンダリングするように指示できます。速度を優先するときは、CodecQ の設定値を小さくするとよい場合もあります。
フレームの伸張
シーケンスが開始されると、DecompressSequenceFrameS または DecompressSequenceFrameWhen を呼び出すことによって、シーケンスの各フレームは伸張のための待ち行列に入ります。図 4 を参照してください。DecompressSequenceBeginS から返された一意の ImageSequence ID を、最初のパラメータとして渡します。
OSErr DecompressSequenceFrameWhen(
ImageSequence seqID, // 一意の segID
Ptr data, // 圧縮データへの
// ポインタ
long dataSize, // データバッファのサイズ
CodecFlags inFlags, // 制御フラグ
CodecFlags *outFlags, // ステータスフラグ
ICMCompletionProcRecordPtr asyncCompletionProc, // 非同期完了
// プロシージャレコード
const ICMFrameTimeRecord *frameTime); // フレームの時間情報
|
図 4
DecompressSequenceFrameWhen API
|
ICM は、伸張処理を管理し、適切なコーデックコンポーネントを呼び出して作業を行います。その結果、DecompressSequenceBeginS の呼び出しで指定された場所にフレームが伸張されます。
フレームを伸張します。リスト 4 を参照してください。
// DecompressSequenceFrameWhen を包むシンプルな
// ラッパー。完了処理と frameTime を指定しない。
// イン/アウトフラグをし、
// 伸張処理は即座に実行される。
OSErr DecompressFrameNow(ImageSequence inSequenceID,
Ptr inBuffer, long inBufferSize)
{
return DecompressSequenceFrameWhen(inSequenceID,
inBuffer, inBufferSize, 0, NULL, NULL, NULL);
}
|
リスト 4a DecompressSequenceFrameWhen を包むシンプルなラッパー
|
// 次のような構造体を使えば、フレームごとに情報を
// トラッキングできる。
typedef struct {
ImageSequence seqID; // 伸張シーケンス ID
Ptr pSrcBuffer; // 圧縮データへのポインタ
long bufSize; // 圧縮画像データのサイズ
ICMCompletionProcRecordPtr
pCompletionProc; // ICMCompletionProcRecord への
// ポインタ
Boolean isDestDone; // ICM が出力先を使い終ったかどうか
OSErr rc; // リターンコード
} FrameRecord, *FrameRecordPtr, **FrameRecordHdl;
// DecompressSequenceFrameWhen を包む別の方法。
// FrameRecordPtr は、アプリケーションに関連付けられている
// フレームごとに情報をトラッキングする何らかの型のデータ構造体
// とする。
OSErr DecompressFrame(FrameRecordPtr inFrame)
{
if (inFrame->pCompletionProc) {
// 完了処理を用意する場合は、呼び出しがあった
// 場合に引き出されるようにフレームを格納しておく
inFrame->pCompletionProc->completionRefCon = (long)inFrame;
}
return DecompressSequenceFrameWhen(inFrame->seqID,
inFrame->pSrcBuffer, inFrame->bufSize,
0, NULL, inFrame->pCompletionProc, NULL);
}
|
リスト 4b DecompressSequenceFrameWhen を包む別のラッパー
|
非同期伸張
ICMCompletionProcRecord に RefCon と完了関数を指定することによって、伸張を非同期に実行できます。図 5a を参照してください。この場合、デコンプレッサが、codecCompletionDest フラグをセットした状態で完了関数を呼び出し、処理が完了したことを示すまで、伸張された画像を読み込まないようにする必要があります。リスト 5 を参照してください。
完了関数は何回か呼び出されることがあるため、割り込みセーフでなければなりません。asyncCompletionProc に NULL を渡すことは、同期圧縮であることを示します。
さらに、DecompressSequenceFrameWhen に、ICMFrameTimeRecord へのポインタを渡せば、定期伸張を行うこともできます。図 5b を参照してください。この構造体には、フレームが伸張される時間、所要時間、および再生速度を含む、フレームの時間情報を指定するパラメータが含まれています。定期伸張を実装する場合、上で説明した ICMCompletionProc も必ず実装してください。このパラメータには NULL も指定できます。その場合は、伸張処理が即座に実行されます。
// 画像圧縮完了コールバックを指定する
struct ICMCompletionProcRecord {
ICMCompletionUPP completionProc; // ICMCompletionProc に
// アクセスする UPP
long completionRefCon; // このコールバックで使う refcon
};
typedef struct ICMCompletionProcRecord
ICMCompletionProcRecord;
typedef ICMCompletionProcRecord *
ICMCompletionProcRecordPtr;
// 非同期処理の完了時に、コンプレッサコンポーネント
// に呼び出される
typedef void (*ICMCompletionProcPtr) (OSErr result,
short flags, long refcon);
void MyICMCompletionProc(
OSErr result // 現在の処理の結果
short flags // どの処理が完了したか
// を示すフラグ
long refcon); // ICMCompletionProcRecord で
// 指定されたrefcon
|
図 5a
ICMCompletionProcRecord および
ICMCompletionProc
|
// 定期非同期伸張処理用の
// フレームの時間情報を含む
struct ICMFrameTimeRecord {
wide value; // フレームが表示される時間
long scale; // フレームの表示時間の単位
void * base; // 時間のベース
long duration; // フレーム表示の所要時間
// scale フィールドで指定された
// 単位と同じでなければならない
// 所要時間が不明の場合は 0
Fixed rate; // 時間のベースの実効速度
long recordSize; // この構造体のサイズ
long frameNumber; // フレーム番号が不明の場合は 0
long flags; // フラグ
wide virtualStartTime; // 概念上の開始時間
long virtualDuration; // 概念上の所要時間
};
|
図 5b
ICMFrameTimeRecord
|
// ICM の伸張完了プロシージャのサンプル。
// このプロシージャでは、ICM が伸張先バッファを使い終わったかどうかを
// 示す codecCompletionDest フラグのステータスをチェックするだけ。
// FrameRecordPtr は、アプリケーションに関連付けられている、
// フレームごとに情報をトラッキングする何らかの型のデータ構造体
// とする。
// 注:関数は何回か呼び出されることがあるため、割り込みセーフで
// なければならない。
static pascal void DecompressionDone(OSErr inResult,
short inFlags, long inRefCon)
{
FrameRecordPtr theFrame = (FrameRecordPtr)inRefCon;
if (noErr == inResult) {
if (codecCompletionDest & inFlags) {
// ICM は出力先を使い終わった
theFrame->isDestDone = true;
...
}
}
theFrame->rc = inResult;
}
|
リスト 5 ICM の伸張完了プロシージャのサンプル
|
ピクセルへのアクセス
ピクセルに直接アクセスするには、GetGWorldPixMap を使って、伸張先の GWorld から PixMapHandle を取得して、ピクセル画像の先頭へのポインタを戻す GetPixBaseAddr を呼び出します。
GetPixRowBytes または QTGetPixMapHandleRowBytes を使って、ハンドルによってアクセスされるピクセルマップの rowBytes 値(画像データのある行の先頭から、次の行の先頭までの距離(バイト単位))を取得できるのに対し、QTGetPixMapPtrRowBytes は、同じことをポインタによってアクセスされるピクセルマップに対して行います。図 6 を参照してください。
オフスクリーンのピクセル画像のピクセルマップからのコピー中またはそこへの描画中に、ピクセル画像のベースアドレスが移動しないように、必ず LockPixels を呼び出すようにしてください。
// 次のような構造体を使って、32 ビットピクセル
// マップをピクセルの配列として簡単にキャストできる。
typedef struct {
UInt8 alpha; // アルファのコンポーネント
UInt8 red; // 赤のコンポーネント
UInt8 green; // 緑のコンポーネント
UInt8 blue; // 青のコンポーネント
} ARGBPixelRecord, *ARGBPixelPtr, **ARGBPixelHdl;
PixMapHandle hPixMap = GetGWorldPixMap(myDestGWorld);
long theRowBytes = QTGetPixMapHandleRowBytes(hPixMap);
Ptr pPixels = GetPixBaseAddr(hPixMap);
|
図 6 ピクセルへの直接のアクセス
|
シーケンスのセットアップ関数。リスト 6 を参照してください。
シーケンスの終了
シーケンス全体が伸張されたら、CDSequenceEnd を呼び出して、処理を終了します。このとき、一意の画像シーケンス ID を渡します。図 7 を参照してください。
シーケンスの終了関数。リスト 7 を参照してください。
OSErr CDSequenceEnd(ImageSequence seqID);
|
図 7 CDSequenceEnd API
|
// 伸張シーケンスをセットアップする方法の一例を示す
// サンプル関数。
// このサンプルの目的のために、冗長な値の割り当てを行う
// MyAppObjectPtr は、アプリケーションに関連付けられている、重要な
// ビットをすべてトラッキングする、何らかの型のデータ構造体を想定している。
OSErr SetupDecompressionSequenceForDV(MyAppObjectPtr inAppObject)
{
ImageDescriptionHandle hImageDescription = NULL;
GWorldPtr theGWorld = NULL;
ImageSequence theSequenceID = 0;
PixMapHandle hPixMap = NULL;
long theRowBytes = 0;
Ptr pPixels = NULL;
OSErr err = noErr;
hImageDescription = MakeImageDescriptionForDV();
err = MemError();
if (hImageDescription) {
// GWorld が、圧縮された画像とまったく同じサイズである
// 必要はない
err = MakeGWorld(&theGWorld,
(*desc)->width, (*desc)->height);
if (err) goto bail;
err = MakeDecompressionSequence(hImageDescription,
theGWorld, &theSequenceID);
if (err) goto bail;
// ピクセルの BaseAddress および RowBytes を取得する
hPixMap = GetGWorldPixMap(theGWorld);
pPixels = GetPixBaseAddr(hPixMap);
theRowBytes = QTGetPixMapHandleRowBytes(hPixMap);
// これが必要としている情報
inAppObject->dSeqID = theSequenceID;
inAppObject->pGWorld = theGWorld;
inAppObject->pPixels = pPixels;
inAppObject->rowBytes = theRowBytes;
}
bail:
if (hImageDescription)
DisposeHandle((Handle)hImageDescription);
return err;
}
|
リスト 6 伸張シーケンスのセットアップを示すサンプル関数
|
// 伸張シーケンスを終了する方法の一例を示すサンプル。
// MyAppObjectPtr は、アプリケーションに関連付けられ、重要な
// ビットをすべてトラッキングする何らかの型のデータ構造体とする。
OSErr EndDecompressionSequenceForDV(MyAppObjectPtr inAppObject)
{
OSErr err = noErr;
// 伸張シーケンスを終了する
if (0 != inAppObject->dSeqID) {
err = CDSequenceEnd(inAppObject->dSeqID);
inAppObject->dSeqID = 0;
}
// GWorld を破棄する
if (NULL != inAppObject->pGWorld)
DisposeGWorld(inAppObject->pGWorld);
inAppObject->pGWorld = NULL;
inAppObject->pPixels = NULL;
inAppObject->rowBytes = 0;
return err;
}
|
リスト 7 伸張シーケンスの終了方法を示すサンプル関数
|
先頭に戻る
参考文献
QTNewGWorld
ImageDescription
Structure
ImageDescription
Working
with Image Descriptions
DecompressSequenceBeginS
DecompressSequenceFrameWhen
ICMCompletionProcRecord
ICMCompletionProc
ICMFrameTimeRecord
Working
with Sequences
Image
Compression Manager
Decompressing
Sequences
Changing
Sequence-Decompression Parameters
先頭に戻る
ダウンロード
|